using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace AltNetHispano.Vale
{
	public class Validator : IValidator
	{
		readonly Dictionary<Type, List<Func<object, string>>> validatorsHolder 
			= new Dictionary<Type, List<Func<object, string>>>();

		readonly Dictionary<MemberInfo, List<Func<object, string>>> propertyValidatorsHolder
			= new Dictionary<MemberInfo, List<Func<object, string>>>();
		
		public IDictionary<Type, List<Func<object, string>>> ValidatorsHolder
		{
			get { return validatorsHolder; }
		}

		public void Register<T>(Func<T, string> assert)
		{
			var objType = typeof (T);
			List<Func<object, string>> actions;
			if(!validatorsHolder.TryGetValue(objType, out actions))
			{
				actions = new List<Func<object, string>>();
			}
			actions.Add(x=> assert((T) x));
			validatorsHolder[objType] = actions;
		}

		public void Register<T>(Func<T, string> assert, MemberInfo member)
		{
			List<Func<object, string>> actions;
			if(!propertyValidatorsHolder.TryGetValue(member, out actions))
			{
				actions = new List<Func<object, string>>();
			}
			actions.Add(x => assert((T)x));
			propertyValidatorsHolder[member] = actions;

			Register(assert);
		}

		

		public IEnumerable<string> GetInvalidMessages(object entity)
		{
			if (entity == null)
			{
				throw new ArgumentNullException("entity");
			}
			var objType = entity.GetType();
			List<Func<object, string>> actions = GetValidationActions(objType);
			return actions.Select(action => action(entity)).GetNotEmptyStrings();
		}

		public IEnumerable<string> GetInvalidMessages(object entity, MemberInfo member)
		{
			List<Func<object, string>> actions;
			if (propertyValidatorsHolder.TryGetValue(member, out actions))
			{
				return actions.Select(a => a(entity)).Where(r => !string.IsNullOrEmpty(r));
			}
			return new string[] { };
		}
		
		public bool IsValid(object entity)
		{
			return !GetInvalidMessages(entity).Any();
		}

		public List<Func<object, string>> GetValidationActions(Type entityType)
		{
			var actions= new List<Func<object, string>>();
			foreach (var candidateValidatorType in entityType.GetAllBaseTypesWithInterfaces())
			{
				List<Func<object, string>> specificTypeActions;
				validatorsHolder.TryGetValue(candidateValidatorType, out specificTypeActions);
				actions.AddRange(specificTypeActions ?? new List<Func<object, string>>());
			}
			return actions;
		}
	}
}